/* eslint-disable eqeqeq */
/* eslint-disable no-param-reassign */
/* eslint-disable jsdoc/require-param-description */
/* eslint-disable no-bitwise */
/* eslint-disable jsdoc/check-tag-names */
/**
 * @1900-2100区间内的公历、农历互转
 * @charset  UTF-8
 * @Time  2014-7-21
 * @Version  $ID$
 * @公历转农历：solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
 * @农历转公历：lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
 * @link http://blog.jjonline.cn/userInterFace/173.html
 * @author  Ajing(JJonline@JJonline.Cn)
 */

import lunarInfo from './const/lunarInfo';
import solarMonth from './const/solarMonth';
import gan from './const/gan';
import zhi from './const/zhi';
import animals from './const/animals';
import lunarTerm from './const/lunarTerm';
import lTermInfo from './const/lTermInfo';
import nStr1 from './const/nStr1';
import nStr2 from './const/nStr2';
import nStr3 from './const/nStr3';
import nStr4 from './const/nStr4';
import lunarFestival from './const/lunarFestival';
import festival from './const/festival';
import { type Solar2lunar } from './types';
/**
 * 返回农历y年一整年的总天数
 * @param lunar - Year
 * @returns Number
 * @eg:var count = lYearDays(1987) ;//count=387
 */
export function lYearDays(y: number): number {
  let i = undefined;
  let sum = 348;
  for (i = 0x8000; i > 0x8; i >>= 1) {
    sum += lunarInfo[y - 1900]! & i ? 1 : 0;
  }
  return sum + leapDays(y);
}

/**
 * 返回农历y年闰月的天数 若该年没有闰月则返回0
 * @param lunar - Year
 * @returns Number (0、29、30)
 * @eg:var leapMonthDay = leapDays(1987) ;//leapMonthDay=29
 */
export function leapDays(y: number): number {
  if (leapMonth(y)) {
    return lunarInfo[y - 1900]! & 0x10000 ? 30 : 29;
  }
  return 0;
}

/**
 * 返回农历 y 年 m 月（非闰月）的总天数，计算 m 为闰月时的天数请使用 leapDays 方法
 * @param lunar - Year
 * @returns Number (-1、29、30)
 * @eg:var MonthDay = monthDays(1987,9) ;//MonthDay=29
 */
export function monthDays(y: number, m: number): number {
  if (m > 12 || m < 1) {
    return -1;
  } //月份参数从1至12，参数错误返回-1
  return lunarInfo[y - 1900]! & (0x10000 >> m) ? 30 : 29;
}

/**
 * 返回公历(!)y年m月的天数
 * @param solar - Year
 * @returns Number (-1、28、29、30、31)
 * @eg:var solarMonthDay = leapDays(1987) ;//solarMonthDay=30
 */
export function solarDays(y: number, m: number) {
  if (m > 12 || m < 1) {
    return -1;
  } //若参数错误 返回-1
  const ms = m - 1;
  if (ms == 1) {
    //2月份的闰平规律测算后确认返回28或29
    return (y % 4 == 0 && y % 100 !== 0) || y % 400 == 0 ? 29 : 28;
  } else {
    return solarMonth[ms];
  }
}

/**
 * 传入offset偏移量返回干支
 * @param offset - 相对甲子的偏移量
 * @returns Cn string
 */
export function toGanZhi(offset: number): string {
  return gan[offset % 10]! + zhi[offset % 12];
}

/**
 * 传入公历(!) y 年获得该年第 n 个节气的公历日期
 * @param y公历年(1900-2100)；n二十四节气中的第几个节气(1~24)；从n=1(小寒)算起
 * @returns number Number
 * @eg:var _24 = getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
 */
export function getTerm(y: number, n: number): number {
  if (y < 1900 || y > 2100) {
    return -1;
  }
  if (n < 1 || n > 24) {
    return -1;
  }
  const _table = lTermInfo[y - 1900]!;
  const _info = [
    parseInt(`0x${_table.substr(0, 5)}`).toString(),
    parseInt(`0x${_table.substr(5, 5)}`).toString(),
    parseInt(`0x${_table.substr(10, 5)}`).toString(),
    parseInt(`0x${_table.substr(15, 5)}`).toString(),
    parseInt(`0x${_table.substr(20, 5)}`).toString(),
    parseInt(`0x${_table.substr(25, 5)}`).toString(),
  ];
  const _calDay = [
    _info[0]!.substr(0, 1),
    _info[0]!.substr(1, 2),
    _info[0]!.substr(3, 1),
    _info[0]!.substr(4, 2),

    _info[1]!.substr(0, 1),
    _info[1]!.substr(1, 2),
    _info[1]!.substr(3, 1),
    _info[1]!.substr(4, 2),

    _info[2]!.substr(0, 1),
    _info[2]!.substr(1, 2),
    _info[2]!.substr(3, 1),
    _info[2]!.substr(4, 2),

    _info[3]!.substr(0, 1),
    _info[3]!.substr(1, 2),
    _info[3]!.substr(3, 1),
    _info[3]!.substr(4, 2),

    _info[4]!.substr(0, 1),
    _info[4]!.substr(1, 2),
    _info[4]!.substr(3, 1),
    _info[4]!.substr(4, 2),

    _info[5]!.substr(0, 1),
    _info[5]!.substr(1, 2),
    _info[5]!.substr(3, 1),
    _info[5]!.substr(4, 2),
  ];
  return parseInt(_calDay[n - 1]!);
}

/**
 * 传入农历年份数字返回汉语通俗表示法
 * @param lunar - year
 * @returns string
 * @eg:
 */
export function toChinaYear(y: number): string {
  //年 => \u5E74
  const oxxx = parseInt((y / 1000).toString());
  const xoxx = parseInt(((y % 1000) / 100).toString());
  const xxox = parseInt(((y % 100) / 10).toString());
  const xxxo = y % 10;

  return `${nStr4[oxxx]! + nStr4[xoxx] + nStr4[xxox] + nStr4[xxxo]}\u5E74`;
}

/**
 * 传入农历数字月份返回汉语通俗表示法
 * @param lunar - month
 * @returns number string
 * @eg:var cnMonth = toChinaMonth(12) ;//cnMonth='腊月'
 */
export function toChinaMonth(m: number): number | string {
  // 月 => \u6708
  if (m > 12 || m < 1) {
    return -1;
  } //若参数错误 返回-1
  let s = nStr3[m - 1];
  s += '\u6708'; //加上月字
  return s!;
}

/**
 * 传入农历日期数字返回汉字表示法
 * @param lunar - day
 * @returns Cn string
 * @eg:var cnDay = toChinaDay(21) ;//cnMonth='廿一'
 */
export function toChinaDay(d: number): string {
  //日 => \u65e5
  let s = undefined;
  switch (d) {
    case 10:
      s = '\u521d\u5341';
      break;
    case 20:
      s = '\u4e8c\u5341';
      break;
    case 30:
      s = '\u4e09\u5341';
      break;
    default:
      s = nStr2[Math.floor(d / 10)];
      s += nStr1[d % 10]!;
  }
  return s!;
}

/**
 * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
 * @param y - year
 * @returns Cn string
 * @eg:var animal = getAnimal(1987) ;//animal='兔'
 * todo 生肖需要精确转换
 */
export function getAnimal(y: number): string {
  return animals[(y - 4) % 12]!;
}
/**
 * 返回农历y年闰月是哪个月；若y年没有闰月 则返回0
 * @param lunar - Year
 * @returns Number (0-12)
 * @eg:var leapMonth = solarLunar.leapMonth(1987) ;//leapMonth=6
 */
export function leapMonth(y: number): number {
  //闰字编码 \u95f0
  return lunarInfo[y - 1900]! & 0xf;
}

/**
 * 传入公历年月日获得详细的公历、农历object信息 <=>JSON
 * @param y - solar year
 * @param m - solar month
 * @param d - solar day
 * @returns JSON object
 * @eg:console.log(solar2lunar(1987,11,01));
 */
export function solar2lunar(y: number, m: number, d: number): Solar2lunar | number {
  //参数区间1900.1.31~2100.12.31
  if (y < 1900 || y > 2100) {
    return -1;
  } //年份限定、上限
  if (y == 1900 && m == 1 && d < 31) {
    return -1;
  }
  //未传参 获得当天
  const objDate = !y ? new Date() : new Date(y, parseInt(m.toString()) - 1, d);

  let i = undefined;
  let leap = 0;
  let temp = 0;
  //修正ymd参数
  y = objDate.getFullYear();
  m = objDate.getMonth() + 1;
  d = objDate.getDate();
  let offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000;
  for (i = 1900; i < 2101 && offset > 0; i++) {
    temp = lYearDays(i);
    offset -= temp;
  }
  if (offset < 0) {
    offset += temp;
    i--;
  }

  //是否今天
  const isTodayObj = new Date();
  let isToday = false;
  if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
    isToday = true;
  }
  //星期几
  let nWeek = objDate.getDay();
  const cWeek = nStr1[nWeek];
  if (nWeek == 0) {
    nWeek = 7;
  } //数字表示周几顺应天朝周一开始的惯例
  //农历年
  const year = i;

  leap = leapMonth(i); //闰哪个月

  let isLeap = false;

  //效验闰月
  for (i = 1; i < 13 && offset > 0; i++) {
    //闰月
    if (leap > 0 && i == leap + 1 && isLeap == false) {
      --i;
      isLeap = true;
      temp = leapDays(year); //计算农历闰月天数
    } else {
      temp = monthDays(year, i); //计算农历普通月天数
    }
    //解除闰月
    if (isLeap == true && i == leap + 1) {
      isLeap = false;
    }
    offset -= temp;
  }

  if (offset == 0 && leap > 0 && i == leap + 1) {
    if (isLeap) {
      isLeap = false;
    } else {
      isLeap = true;
      --i;
    }
  }
  if (offset < 0) {
    offset += temp;
    --i;
  }
  //农历月
  const month = i;
  //农历日
  const day = offset + 1;

  //天干地支处理
  const sm = m - 1;
  const term3 = getTerm(y, 3); //该公历年立春日期
  let gzY = toGanZhi(y - 4); //普通按年份计算，下方尚需按立春节气来修正
  const termTimestamp = new Date(y, 1, term3).getTime();
  const dayTimestamp = new Date(y, sm, d).getTime();
  //依据立春日进行修正gzY
  if (dayTimestamp < termTimestamp) {
    gzY = toGanZhi(y - 5);
  }

  //月柱 1900年1月小寒以前为 丙子月(60进制12)
  const firstNode = getTerm(y, m * 2 - 1); //返回当月「节」为几日开始
  const secondNode = getTerm(y, m * 2); //返回当月「节」为几日开始

  //依据12节气修正干支月
  let gzM = toGanZhi((y - 1900) * 12 + m + 11);
  if (d >= firstNode) {
    gzM = toGanZhi((y - 1900) * 12 + m + 12);
  }

  //传入的日期的节气与否
  let isTerm = false;
  let term = '';
  if (firstNode == d) {
    isTerm = true;
    term = lunarTerm[m * 2 - 2]!;
  }
  if (secondNode == d) {
    isTerm = true;
    term = lunarTerm[m * 2 - 1]!;
  }
  //日柱 当月一日与 1900/1/1 相差天数
  const dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10;
  const gzD = toGanZhi(dayCyclical + d - 1);
  let festival1: string = lunarFestival[month] && lunarFestival[month][day] && '';
  if ([2022, 2025, 2026, 2027, 2028, 2029, 2031, 2032].includes(y) && month == 12 && day == 29) {
    festival1 = '除夕';
  }
  const festival2: string = festival[m] && festival[m][d] && '';
  return {
    lYear: year,
    lMonth: month,
    lDay: day,
    animal: getAnimal(year),
    yearCn: toChinaYear(year),
    monthCn: (isLeap && leap == month ? '\u95f0' : '') + toChinaMonth(month),
    dayCn: toChinaDay(day),
    cYear: y,
    cMonth: m,
    cDay: d,
    gzYear: gzY,
    gzMonth: gzM,
    gzDay: gzD,
    isToday,
    isLeap,
    nWeek,
    ncWeek: `\u661f\u671f${cWeek}`,
    isTerm,
    term,
    lunarFestival: festival1,
    festival: festival2,
  } as Solar2lunar;
}
/**
 * 传入公历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
 * @param y - lunar year
 * @param m - lunar month
 * @param d - lunar day
 * @param isLeapMonth - lunar month is leap or not.
 * @returns JSON object
 * @eg:console.log(solarLunar.lunar2solar(1987,9,10));
 */
export function lunar2solar(y: number, m: number, d: number, isLeapMonth: boolean): Solar2lunar | number {
  //参数区间1900.1.31~2100.12.1
  const leapMonth1 = leapMonth(y);
  if (isLeapMonth && leapMonth1 !== m) {
    return -1;
  } //传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
  if ((y == 2100 && m == 12 && d > 1) || (y == 1900 && m == 1 && d < 31)) {
    return -1;
  } //超出了最大极限值
  const day = monthDays(y, m);
  if (y < 1900 || y > 2100 || d > day) {
    return -1;
  } //参数合法性效验

  //计算农历的时间差
  let offset = 0;
  for (let i = 1900; i < y; i++) {
    offset += lYearDays(i);
  }
  let isAdd = false;
  let leap = 0;
  for (let i = 1; i < m; i++) {
    leap = leapMonth(y);
    if (!isAdd) {
      //处理闰月
      if (leap <= i && leap > 0) {
        offset += leapDays(y);
        isAdd = true;
      }
    }
    offset += monthDays(y, i);
  }
  //转换闰月农历 需补充该年闰月的前一个月的时差
  if (isLeapMonth) {
    offset += day;
  }
  //1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
  const stmap = Date.UTC(1900, 1, 30, 0, 0, 0);
  const calObj = new Date((offset + d - 31) * 86400000 + stmap);
  const cY = calObj.getUTCFullYear();
  const cM = calObj.getUTCMonth() + 1;
  const cD = calObj.getUTCDate();

  return solar2lunar(cY, cM, cD);
}
